import { TrackOpType, TriggerOpType } from "./const"
import { Stack } from "./stack"
import type { EffectOption } from "./type"


let activeEffect:Function|null = null

const targetMap = new WeakMap<object,Map<string|symbol, Set<Function>>>()

// solve side effect function nesting problem
const effectStack = new Stack<Function>()


// solve the conditional branching problem
function cleanup(effect:any) {
  const { deps } = effect

  if(deps.length) {
    for(let i = 0; i < deps.length; i++) {
      deps[i].delete(effect)
    }
    deps.length = 0
  }
}

function createReactiveEffect(effect:Function, option:object) {
  const effectFn = ()=>{
    try {
      effectStack.push(effectFn)
      activeEffect = effect
      effect()
    } finally {
      effectStack.pop()
      activeEffect = effectStack.get(effectStack.length-1) ?? null
    }
  }

  effectFn.deps = [] as string[]

  return effectFn
}

export function effect(fn:Function, option:EffectOption={}) {
  const effectFn = createReactiveEffect(fn, option)
  
  effectFn()

  return effectFn
}


export function track(target:object, type:TrackOpType, key:string|symbol) {
  if(activeEffect == null) return

  if(!targetMap.has(target)) {
    const depsMap = new Map()
    targetMap.set(target, depsMap)
  }
  
  if(!targetMap.get(target)?.has(key)) {
    const effects = new Set<Function>()
    targetMap.get(target)?.set(key, effects)
  }

  targetMap.get(target)?.get(key)?.add(activeEffect) 
}

export function trigger(target:object, type:TriggerOpType, key:string|symbol) {
  targetMap.get(target)?.get(key)?.forEach(fn=>fn())
}
